package edu.cornell.cubooks.controller;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;

import org.hibernate.ObjectNotFoundException;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;

import edu.cornell.cubooks.CUBooksConstants;
import edu.cornell.cubooks.dao.BidDao;
import edu.cornell.cubooks.dao.FeedbackDao;
import edu.cornell.cubooks.dao.PostingDao;
import edu.cornell.cubooks.dao.UserDao;
import edu.cornell.cubooks.domain.Bid;
import edu.cornell.cubooks.domain.Feedback;
import edu.cornell.cubooks.domain.Posting;
import edu.cornell.cubooks.domain.User;
import edu.cornell.cubooks.exception.PostingNotFoundException;
import edu.cornell.cubooks.listener.LoginListener;
import edu.cornell.cubooks.utils.CurrentUser;
import edu.cornell.cubooks.validator.BidValidator;
import edu.cornell.cubooks.validator.PostingValidator;
import edu.cornell.cubooks.validator.FeedbackValidator;

/**
 * Posting controller which handles all requests related to postings and biding.
 * 
 * @author CUBooks team
 *
 */
@Controller
@SessionAttributes({"posting","bid","feedback"})
public class PostingsController {
	
	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LoginListener.class);	
	private PostingDao postingDao;
	private BidDao bidDao;
	private FeedbackDao feedbackDao;
	private UserDao userDao;
	
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }
	
	@RequestMapping("/postings/index.html")
    public String indexHandler(Model model) {
		Set<Posting> postings = null;
		try {
			// return all active postings
			postings =  getPostingDao().getAllByStatus(CUBooksConstants.STATUS_ACTIVE);
			
			// check if any return postings should be closed
			postings = getPostingDao().closeAll(postings);

			Posting posting = new Posting();
			model.addAttribute(postings);
			model.addAttribute(posting);
			return "postings/index";
		}   
		catch(ObjectNotFoundException e) {
			return "postings/index";
		}
	}

	@RequestMapping(value = "/postings/search.html", method = RequestMethod.POST)
	public String searchHandler(@ModelAttribute Posting posting, BindingResult result, SessionStatus status, Model model) {
		Set<Posting> postings = null;
		postings = getPostingDao().getAllBySearchTitle(posting.getTitle());
		// check if any return postings should be closed
		postings = getPostingDao().closeAll(postings);
		
		model.addAttribute(postings);
		model.addAttribute(posting);
		return "/postings/index";
	}		
	
	@SuppressWarnings("unchecked")
	@RequestMapping(value = "/postings/show.html", method = RequestMethod.GET)
    public void showHandler(@RequestParam("id") int id, ModelMap model) throws PostingNotFoundException {
		// fetch posting for id
		Posting posting = getPostingDao().get(id);	
		User user = CurrentUser.getUser();
			
		if (posting == null) {
			throw new PostingNotFoundException("Posting " + id + " not found.");
		} else if (posting.getStatus() == CUBooksConstants.STATUS_OPEN && (user == null || !user.getId().equals(posting.getUser().getId()))) {
			throw new PostingNotFoundException("Posting " + id + " not found.");
		}
		// check if posting is closable
		if (getPostingDao().isClosable(posting)) {
			posting =  getPostingDao().close(posting);
		}
		
		// load all feedbacks for the posting's seller  
		posting.getUser().setFeedbacks(getFeedbackDao().getFeedbacksForSeller(posting.getUser()));
		model.put("posting", posting);
		
		// load default bid
		Bid bid = new Bid();
		bid.setPosting(posting);
		bid.setUser(CurrentUser.getUser());
		model.put("bid", bid);
		
		// load default feedback
		Feedback feedback = new Feedback();
		feedback.setPosting(posting);
		feedback.setUser(posting.getUser());
		model.put("feedback", feedback);
	}
	
	@RequestMapping(value = "/postings/mine.html", method = RequestMethod.GET)
    public ModelMap mineHandler() {
    	return new ModelMap(getPostingDao().getMine(CurrentUser.getUser()));
	}
	
	@RequestMapping(value = "/postings/start.html", method = RequestMethod.GET)
     public String startHandler(@RequestParam("id") int id, ModelMap model) {
		Posting posting = getPostingDao().get(id);
		User user = CurrentUser.getUser();
		if (user.getId().equals(posting.getUser().getId())) {
			Date date = new Date();
			posting.setStartDate(date);
			posting.setStatus(CUBooksConstants.STATUS_ACTIVE);
			getPostingDao().save(posting);
			LOG.info("Status of posting with Id: " + id + " changed to active at " + new Date());
		}
		return "redirect:/user/home.html";
	}
	
	@RequestMapping(value = "/postings/delete.html", method = RequestMethod.GET)
    public String deleteHandler(@RequestParam("id") int id) throws PostingNotFoundException {
		Posting posting = getPostingDao().get(id);
		if (posting == null) {
			throw new PostingNotFoundException("Posting " + id + " not found.");
		}
		// if current user equals user associated with posting
		if(CurrentUser.getUser().equals(posting.user) && posting.getStatus() == CUBooksConstants.STATUS_OPEN) {
			getPostingDao().deleteById(id);
			LOG.info("Posting with Id: " + id + " was deleted at " + new Date());
			return "redirect:/user/home.html";
		}
		
		return "redirect:/access_denied.jsp";
	}

	@RequestMapping(value = "/postings/add.html", method = RequestMethod.GET)
    public String addHandler(Model model) {
		Posting posting = new Posting();
		model.addAttribute(posting);
		return "postings/add";
	}
	
	@RequestMapping(value = "/postings/edit.html", method = RequestMethod.GET)
    public String editHandler(@RequestParam("id") int id, Model model) throws PostingNotFoundException {
		Posting posting = getPostingDao().get(id);
		if (posting == null) {
			throw new PostingNotFoundException("Posting " + id + " not found.");
		}
		if(CurrentUser.getUser().equals(posting.user) && posting.getStatus() == CUBooksConstants.STATUS_OPEN) {
			model.addAttribute(posting);
			LOG.info("Posting with Id: " + id + " was edited at " + new Date());
			return "postings/edit";
		}
		return "redirect:/access_denied.jsp";
	}
	
	@RequestMapping(method = RequestMethod.POST)
	public String processSubmit(@ModelAttribute Posting posting, BindingResult result, SessionStatus status) {
		new PostingValidator().validate(posting, result);
	
		if (result.hasErrors()) {
			return "/postings/add";
		}
		else {
			posting.setUser(CurrentUser.getUser());
			posting = getPostingDao().save(posting);
			status.setComplete();
			LOG.info("New posting with Id: " + posting.getId() + " added at " + new Date());
			return "redirect:/user/home.html";
		}
	}
	
	@RequestMapping(value = "/postings/addBid.html", method = RequestMethod.POST)
	public String processSubmit(@ModelAttribute Bid bid, BindingResult result, SessionStatus status, Model model) {
		BidValidator bidValidator = new BidValidator(bidDao);
		bidValidator.validate(bid, result);
		Posting posting = getPostingDao().get(bid.getPosting().getId());	
		if (getPostingDao().isClosable(posting)) {
			posting =  getPostingDao().close(posting);
			model.addAttribute("bidplacedaftertime",1);
			return "redirect:/postings/show/" + bid.getPosting().getId() + "/";
		} else if (result.hasErrors()) {
			return "postings/show";
		} else {
			try {
				getBidDao().placeBid(bid);
				status.setComplete();
				return "redirect:/postings/show/" + bid.getPosting().getId() + "/";
			}
			catch (ConstraintViolationException e) {
				model.addAttribute("bidnotaccepted", 1);
				return "redirect:/postings/show/" + bid.getPosting().getId() + "/";
			}
		}
	}
	
	@RequestMapping(value = "/postings/addFeedback.html", method = RequestMethod.POST)
	public String processSubmit(@ModelAttribute Feedback feedback, BindingResult result, SessionStatus status) {
		FeedbackValidator bidValidator = new FeedbackValidator();
		bidValidator.validate(feedback, result);
		if (result.hasErrors()) {
			return "postings/show";
		} else {
			getFeedbackDao().save(feedback);
			status.setComplete();
			LOG.info("Feedback added for posting with Id: " + feedback.posting.getId() + " at " + new Date());
			return "redirect:/postings/show/" + feedback.getPosting().getId() + "/";
		}
	}
	
	public PostingDao getPostingDao() {
		return postingDao;
	}
	
	@Autowired
	public void setPostingDao(PostingDao postingDao) {
		this.postingDao = postingDao;
	}

	public BidDao getBidDao() {
		return bidDao;
	}
	
	@Autowired
	public void setBidDao(BidDao bidDao) {
		this.bidDao = bidDao;
	}

	public FeedbackDao getFeedbackDao() {
		return feedbackDao;
	}

	@Autowired
	public void setFeedbackDao(FeedbackDao feedbackDao) {
		this.feedbackDao = feedbackDao;
	}

	public UserDao getUserDao() {
		return userDao;
	}

	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}
}
